with
語法今天的主題是上下文管理器與 with
語法。上下文管理器允許你在執行特定的區塊代碼前後自動管理資源,例如開啟和關閉文件、連接和斷開數據庫等。本課程將帶領 0 基礎的 Python 學生學習如何使用上下文管理器來簡化代碼並確保資源得到妥善處理。
在程式開發中,經常需要執行一些資源管理任務,比如打開文件、連接資料庫或加鎖共享資源。這些資源在不使用時應當被正確釋放,以避免記憶體洩漏或其他問題。上下文管理器能自動幫助我們管理這些資源。
例子:手動打開與關閉文件
file = open('example.txt', 'r')
try:
data = file.read()
finally:
file.close() # 確保文件被正確關閉
這段代碼可以使用上下文管理器進行簡化。
with
語法介紹with
語法讓你不必手動管理資源。它會在 with
區塊開始時自動處理打開資源,並在結束時自動處理釋放資源。
例子:使用 with
簡化文件操作
with open('example.txt', 'r') as file:
data = file.read()
# 文件自動被關閉,不需顯式調用 close()
Python 提供了魔術方法 __enter__
和 __exit__
來創建自定義上下文管理器。__enter__
方法定義了上下文管理器的行為開始,而 __exit__
定義了結束時的清理操作。
例子:創建自定義上下文管理器
class CustomContext:
def __enter__(self):
print("進入上下文")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("退出上下文")
with CustomContext():
print("正在執行上下文內的代碼")
執行結果:
進入上下文
正在執行上下文內的代碼
退出上下文
上下文管理器不僅可以自動管理資源,還能處理上下文區塊內發生的異常。當上下文區塊內的代碼引發異常時,__exit__
方法會捕捉異常並做適當處理。
例子:自定義上下文管理器處理異常
class CustomContext:
def __enter__(self):
print("開始")
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
print(f"捕捉到異常: {exc_type}")
print("結束")
return True # 告訴 Python 異常已處理,不需要拋出
with CustomContext():
raise ValueError("這是個異常")
練習 1: 創建一個上下文管理器,用來追蹤代碼的執行時間。
練習 2: 設計一個上下文管理器,用來自動鎖定和解鎖某個共享資源。
要建立一個上下文管理器來追蹤代碼的執行時間,可以使用 Python 的 contextlib
模組中的 @contextmanager
裝飾器,或者通過自定義一個類並實現 __enter__()
和 __exit__()
方法。這裡我將使用類的方法,因為這樣會使上下文管理器更加清晰和結構化。
我們將使用 Python 的 time
模組來記錄開始和結束時間,然後計算出代碼執行的總時間。以下是具體實現:
import time
class ExecutionTimer:
def __enter__(self):
# 記錄開始時間
self.start_time = time.time()
print("開始追蹤代碼執行時間...")
return self # 返回上下文管理器對象本身
def __exit__(self, exc_type, exc_val, exc_tb):
# 記錄結束時間
self.end_time = time.time()
# 計算並顯示執行時間
execution_time = self.end_time - self.start_time
print(f"代碼執行完成,耗時: {execution_time:.4f} 秒")
你可以使用 with
語句來簡單地實現代碼執行時間的追蹤。所有放在 with
區塊中的代碼都會被計算執行時間。
with ExecutionTimer():
# 模擬一段代碼執行
for i in range(1000000):
pass # 這裡可以放入任何你想追蹤的代碼
with
區塊時,__enter__()
方法被調用並記錄當前時間。with
區塊時,__exit__()
方法被觸發,計算從開始到結束的時間並打印出來。這樣的上下文管理器可以很方便地用來測試代碼性能,尤其是需要追蹤某段代碼的執行效率時。
在練習 2 中,我們將設計一個上下文管理器,用來自動鎖定和解鎖某個共享資源。這種技術通常用於多線程編程中,確保某個資源在同一時間內只被一個線程訪問,避免競態條件的發生。Python 提供了 threading
模組中的 Lock
物件來處理這種情況,我們可以用上下文管理器來自動管理資源的鎖定與解鎖。
我們將定義一個上下文管理器,使用 Python 的 threading.Lock
來鎖定和解鎖共享資源。當我們進入上下文(with
區塊)時,鎖會被鎖定;當我們離開上下文時,鎖會自動解鎖。
import threading
class ResourceLocker:
def __init__(self, lock):
self.lock = lock # 傳入一個Lock對象
def __enter__(self):
# 嘗試鎖定資源
print("正在鎖定資源...")
self.lock.acquire()
print("資源已鎖定")
return self # 返回上下文管理器對象本身
def __exit__(self, exc_type, exc_val, exc_tb):
# 解鎖資源
print("正在解鎖資源...")
self.lock.release()
print("資源已解鎖")
可以使用 threading.Lock
來管理共享資源的鎖定與解鎖,並用上下文管理器自動處理這一過程:
# 創建一個Lock對象
lock = threading.Lock()
# 使用上下文管理器來鎖定和解鎖資源
with ResourceLocker(lock):
# 在這個區塊內執行的代碼會獲取鎖
print("共享資源正在被訪問...")
# 可以在這裡訪問共享資源,例如修改全局變數、文件操作等
for i in range(5):
print(f"執行任務 {i+1}")
__enter__()
:當進入 with
區塊時,會調用這個方法鎖定資源。它使用 self.lock.acquire()
來嘗試獲取鎖。__exit__()
:當離開 with
區塊時,這個方法會自動被調用來釋放鎖,保證即使發生異常,鎖也會被正確地釋放。在多線程環境中,這樣的上下文管理器尤其有用。可以確保只有一個線程在訪問共享資源,而不會產生競態條件。例如:
import threading
import time
# 創建一個Lock對象
lock = threading.Lock()
def task():
with ResourceLocker(lock):
print(f"線程 {threading.current_thread().name} 正在訪問共享資源...")
time.sleep(1)
print(f"線程 {threading.current_thread().name} 完成了共享資源的訪問")
# 創建多個線程
threads = []
for i in range(3):
t = threading.Thread(target=task)
threads.append(t)
t.start()
# 等待所有線程完成
for t in threads:
t.join()
在這個例子中,3 個線程會依次獲取鎖,確保同一時間只有一個線程能夠訪問共享資源。
contextlib
模塊中的 contextmanager
Python 還提供了一個便捷的工具,contextlib.contextmanager
裝飾器,它能讓你使用生成器的方式簡單創建上下文管理器。
例子:使用 contextlib.contextmanager
創建上下文管理器
from contextlib import contextmanager
@contextmanager
def simple_context():
print("開始")
yield
print("結束")
with simple_context():
print("正在執行上下文內的代碼")
今天我們學習了上下文管理器和 with
語法的基本概念。上下文管理器是一個強大的工具,能幫助我們簡化資源管理操作,並在代碼發生異常時自動進行清理工作。這是一個確保資源安全釋放的重要概念。
明日預告:正則表達式的應用